Skip to content

rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array - #23346

Draft
lupin012 wants to merge 1 commit into
mainfrom
lupin012/fix_trace_filter_error_objects
Draft

rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array#23346
lupin012 wants to merge 1 commit into
mainfrom
lupin012/fix_trace_filter_error_objects

Conversation

@lupin012

@lupin012 lupin012 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #23128

Problem

trace_filter documents its result as Array<TraceEntry>, but filterV3 wrote per-transaction failures into that array as bare error objects and kept iterating, so the envelope still reported success:

"result":[
  {"action":{},"type":"reward","blockNumber":1},
  {"error":{"code":-32000,"message":"protected txn is not supported by signer …"}},
  {"action":{},"type":"reward","blockNumber":3}
]

That item has no type, action or traceAddress, so a strict decoder breaks on it and a lenient one accepts a partial trace as complete. Thirteen failure paths behaved this way: iterator Next, HeaderByNumber, missing header, Body, three json.Marshal calls, TxnByIdxInBlock, AsMessage, and traceFilterTxn's timeout, execution, FinalizeTx and CommitBlock paths — the last four sharing a writeErr closure.

What other clients do

trace_* is not part of execution-apis, which covers eth, debug, engine and txpool only, so there is no official schema to conform to. The de-facto reference is OpenEthereum's trace module, which documents the result as plain "Array — Traces matching given filter", with no error variant among the items.

Decision

#23128 asked us to choose one explicit result contract out of three:

  1. fail the JSON-RPC request when a transaction cannot be traced;
  2. return only valid TraceEntry items and expose partial failures separately;
  3. document a union element type such as TraceEntry | TraceItemError, including how clients should detect incomplete results.

This PR implements the first one. It is what reth and Nethermind already do, so it aligns us with the de-facto contract instead of inventing a third dialect.

Fix

Every one of those paths now propagates the error instead of writing into the array, and traceFilterTxn drops both its writeErr closure and the (nil, nil) "error already on the stream" convention it relied on.

The result array opens on the first exported trace instead of up front. runMethod wraps the handler's stream in a LazyFieldStream, so a field nobody writes to is never emitted, and an error raised before any trace yields an error-only response. Keeping the array open from the start would instead have paired an empty result with the error. When traces were already streamed, runMethod's rs.CloseIfOpen()StackStream.ClosePending seals the half-written array.

Tests

TestFilterSignerReflectsBlockOverridesNumber is inverted: it asserted the error text showed up inside the stream, it now asserts the call fails and the stream stays empty.

TestFilterErrorAfterExportedTracesKeepsValidJSON is new and covers the error arriving after traces were streamed. Filtering blocks 1-3 with no address filter exports both empty blocks' reward traces before block 3's EIP-155-protected transaction is rejected by the overridden pre-Spurious-Dragon signer. The envelope is assembled the way runMethod does it, since sealing the array is the handler's job rather than filterV3's; this follows the existing TestTraceBlockErrorAfterWrite pattern. It asserts the envelope parses, the result array parses (i.e. ClosePending closed it), every item has type, and any error present decodes as a string rather than an object.

Both were confirmed red against main — each fails on An error is expected but got nil — and with that assertion relaxed the new one reproduces the malformed array above.

@lupin012 lupin012 changed the title rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array [WIP] rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array Aug 17, 2026
…he result array

trace_filter documents its result as Array<TraceEntry>, but filterV3 wrote
per-transaction failures into that array as bare {"error":{...}} items and kept
going, so the JSON-RPC envelope still reported success. Those items carry no
type, action or traceAddress, so a strict decoder breaks on them and a lenient
one silently accepts a partial trace as complete.

Every iterator, lookup, marshal and execution failure now propagates instead.
traceFilterTxn loses its "nil result means the error is already on the stream"
convention and just returns the error, which also drops the nil check at the
call site.

The result array now opens on the first exported trace rather than up front.
That way an error raised before any trace leaves runMethod's LazyFieldStream
unwritten, so the response carries only "error" instead of "result" plus
"error". When traces were already streamed, the handler's ClosePending seals
the half-written array.

Fixes #23128
@lupin012
lupin012 force-pushed the lupin012/fix_trace_filter_error_objects branch from 6965718 to a47cae0 Compare August 17, 2026 21:02
@lupin012 lupin012 changed the title [WIP] rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array rpc/jsonrpc: fail trace_filter instead of mixing error objects into the result array Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trace_filter can mix error objects into a successful Array<TraceEntry> result

1 participant